Skip to content

cli: adapter schema --type / sensors --key + api-key create --store-secret#301

Merged
maximelb merged 2 commits into
cli-v2from
cli-adapter-schema-sensors-store-secret
May 31, 2026
Merged

cli: adapter schema --type / sensors --key + api-key create --store-secret#301
maximelb merged 2 commits into
cli-v2from
cli-adapter-schema-sensors-store-secret

Conversation

@maximelb

Copy link
Copy Markdown
Contributor

Three CLI additions that remove friction in automated/agent build flows (split out of the merged #299; this is the remaining commit).

  • cloud-adapter/external-adapter schema --type <t> — resolve one adapter type's config sub-schema from the hive JSON-Schema $defs and render the flat field table (reusing hive's _flatten_schema). Shows where each field lives (e.g. hostname under client_options), preventing unknown field rejections from hand-built records. Unknown --type errors with the valid type list.
  • cloud-adapter/external-adapter sensors --key <adapter> — return the live sensor(s) an adapter produced by matching the record's installation_key (iid) against the sensor iid — the reliable way to get an adapter's SID without decoding the installation key. Falls back to hostname match; reports a clear "not registered yet" when the adapter hasn't delivered events.
  • api-key create --store-secret <name> (+ --store-secret-tag) — atomically mint the key and write its value into the secret hive, collapsing the mint → capture → store → reference chain into one call (the value never transits an intermediate file).

Unit-tested (986 passing; new coverage in test_cli_ergonomics.py, adapter-group subcommand list updated in the lazy-loading regression test) and smoke-tested live.

🤖 Generated with Claude Code

…tore-secret

Three FDE-acceleration additions found while profiling AI agent build runs:

- cloud-adapter/external-adapter `schema --type <t>`: resolve one adapter
  type's config sub-schema from the hive JSON-Schema $defs and render the flat
  field table (reusing hive's _flatten_schema). Shows where each field lives
  (e.g. hostname under client_options), preventing "unknown field" rejections
  from hand-built records. Unknown type errors with the valid type list.

- cloud-adapter/external-adapter `sensors --key <adapter>`: return the live
  sensor(s) an adapter produced by matching the record's installation_key (iid)
  against the sensor iid — the reliable way to get an adapter's SID without
  decoding the installation key. Falls back to hostname match; reports a clear
  "not registered yet" when the adapter hasn't delivered events.

- `api-key create --store-secret <name>` (+ --store-secret-tag): atomically mint
  the key and write its value into the secret hive, collapsing the
  mint -> capture -> store -> reference chain into one call (the value never
  has to transit an intermediate file).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r selector, etag-safe store-secret

- adapter `schema --type` now respects --quiet (consistent with sensors/list-types)
  and its --output json carries the root $defs so nested $refs stay resolvable.
- adapter `sensors --key` filters server-side via a sensor selector
  (iid == "…" / hostname == "…") instead of paging and scanning every sensor.
- api-key create --store-secret reads any existing secret's etag and writes a
  conditional update (no silent lost-update; reports create vs overwrite), and on
  the no-value edge still surfaces the created key before failing.

Tests updated for the selector path and added: schema CLI table/unknown-type,
store-secret new-vs-existing(etag). 990 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maximelb maximelb marked this pull request as ready for review May 31, 2026 03:00
@maximelb maximelb merged commit 7865be3 into cli-v2 May 31, 2026
6 of 7 checks passed
@maximelb maximelb deleted the cli-adapter-schema-sensors-store-secret branch May 31, 2026 03:00
maximelb added a commit that referenced this pull request Jun 19, 2026
* auth login: allow user-scoped API key without --oid (#289)

The validator currently rejects `auth login --uid X --api-key Y`
with "Error: --oid and --api-key are required for API key login.",
even though `--uid` is documented as the flag for user-scoped API
keys. This makes brand-new accounts un-bootstrappable from the CLI
because they have no organization yet — chicken-and-egg, since the
user can't list/create their first org without working CLI auth,
but can't auth without an OID, but doesn't have an OID until they
create an org.

The User API Key itself is a fully valid credential — env-var auth
(LC_UID + LC_API_KEY) works for `auth list-orgs` already, which is
the only call needed to bootstrap from "I have a User API Key" to
"I know my OIDs". The bug was purely in the `auth login` validator
forcing an --oid constraint that the rest of the CLI doesn't need.

Fix: rework the validator to accept either:
  --oid + --api-key  (org-scoped key — existing behavior)
  --uid + --api-key  (user-scoped key — new path, --oid optional)

When neither --oid nor --uid is provided, emit a clear error
distinguishing the two key types. The `write_credentials` call
already handles oid=None correctly (skips writing the field), so
no downstream changes needed.

Tested:
- `auth login --uid X --api-key Y` (no --oid) → succeeds
- `auth list-orgs` against those credentials → returns the user's orgs
- `auth login --api-key Y` (neither --oid nor --uid) → clear error
- `auth login --oid X --api-key Y` (existing org-scoped path) → unchanged
- `auth login --oauth --provider google` → unchanged

* auth login: clear stale oid on user-scoped re-login + tests (#291)

Follow-up to #289. When a user re-runs `auth login --uid X --api-key Y`
in an environment that previously held an `--oid + --api-key` login, the
old `oid` survived in config because `write_credentials` only writes
fields it is given. Subsequent commands would silently pair the new
user-scoped credentials with the stale org context.

Fix it by dropping the `oid` field from the affected env block right
after the write whenever the login was user-scoped (`uid` set, `oid`
unset). Mirrors the post-write cleanup pattern already used by the
OAuth flow for `api_key`.

Also refresh the `--ai-help` text so it advertises the user-scoped
shape (`--uid + --api-key`, no `--oid`) that #289 enabled, and add
unit tests for the validator branches and the stale-oid cleanup --
neither was covered before.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* vulnerability: fix search payload key + complete extension coverage (#293)

The CLI's search dict was sending {"search": <field>, ...} but the
extension's parseSearchOp reads s["field"] — so every --search-* call
was silently dropped server-side. Switch to {"field": ...} and update
the assertion that locked in the wrong shape.

Round out the rest of the surface the extension exposes:

- new flags: --include-enrichment, --filter-via-state on the list
  commands; --normalized-package-name on cve hosts;
  --rollup-subpackages on host packages; --include-enrichment on
  cve get / cve packages
- new subcommands:
  - vuln cve epss-history (query_epss_history)
  - vuln finding resolve / bulk-resolve / list / reset
    (set_finding_resolution, bulk_set_finding_resolution,
    list_finding_resolutions, reset_asset_findings)
  - vuln snapshot list (query_daily_snapshots)
- help text now mentions lc_risk as a valid --sort-by on cve list
  and host packages

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ai: forward X-LC-UID on org-scoped AI session calls (#294)

User API Keys are scoped to a user, not an org, so jwt.limacharlie.io
can only resolve them when the UID accompanies the secret. The AI
SDK sent X-LC-OID + Authorization but never X-LC-UID, so a User API
Key could not authenticate ai start-session (or any ai session
command) and the server rejected it as an invalid org key.

ai-sessions' OrgDualAuthMiddleware already reads X-LC-UID for the
raw-API-key path (and ignores it for JWT auth), so no server change
is needed -- the SDK just has to send it.

Add AI._org_auth_headers() which always sets X-LC-OID and adds
X-LC-UID when the client has a uid, and route both start_session()
and _org_request() through it so the entire org-scoped AI surface
(start-session, session list/get/terminate/history, usage) works
under a User API Key.

Tests: fix the mock_org fixture to set _uid = None (otherwise the
MagicMock auto-vivifies a truthy _uid and leaks X-LC-UID into every
request) and add TestOrgRequestUidHeader covering both the
org-scoped (no header) and user-scoped (header forwarded) cases for
start_session and the _org_request path.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* schema: add 'schema reset' command to rebuild org schemas (#295)

Exposes the existing backend endpoint DELETE /orgs/{oid}/schema
(resetOrgSchemas) through the Python CLI/SDK, mirroring the Go SDK's
Organization.ResetSchemas().

- SDK: Organization.reset_schemas() -> DELETE orgs/{oid}/schema
- CLI: 'limacharlie schema reset', guarded by --confirm (exit 4
  without it), with register_explain text for --ai-help
- Tests: SDK request assertion, CLI tests for the --confirm guard
  and the happy path, plus a 'schema list' regression guard;
  updated the lazy-loading regression's expected subcommand set

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* cli: add --enabled/--disabled flag to hive set commands (#296)

Hive records are created disabled by default, which surprises operators
(and LLMs) who run e.g. `secret set` and expect the record to be live.

Add an `--enabled/--disabled` flag to the create/update commands so a
record can be created and enabled in one shot:

  limacharlie secret set --key foo --input-file foo.yaml --enabled
  limacharlie lookup set --key bar --input-file bar.yaml --enabled
  limacharlie hive   set --hive-name lookup --key baz --input-file f --enabled
  limacharlie dr     set --key my-rule --input-file rule.yaml --enabled

When passed, the flag overrides any usr_mtd.enabled value in the input
file. When omitted, behavior is unchanged: the input file's value (if
any) is preserved, otherwise the server-side default applies.

The change is scoped to the three entry points that create hive
records:
- `_hive_shortcut.py` — covers secret, lookup, playbook, ai-skill,
  cloud-adapter, external-adapter, fp, note, sop
- `hive.py` — generic `hive set`
- `dr.py` — `dr set`

Help text on each updated to call out the disabled-by-default
behavior and the new flag.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* cli: clarify event overview/timeline/list --ai-help to prevent unreachable polling predicates (#297)

A real AI session burned its full 5-minute foreground-Bash timeout polling
"event overview" with `grep -q event_type` — a predicate unreachable by
construction because overview returns only millisecond-epoch timestamp
buckets, no event content. The previous --ai-help said overview gave a
"high-level summary of event activity" without showing the output shape,
which an AI can reasonably read as "richer than just timestamps".

This commit extends the three relevant explain blocks so any AI reading
--ai-help cold can pick the right tool the first time:

- event overview: shows the actual output shape (flat list of ms epochs),
  what empty looks like (`[]`), what overview is good for, and an explicit
  "do not use this for sampling content or for predicates keyed on
  in-payload fields — those are unreachable, you will spin until timeout."

- event timeline: alias, points at event overview's expanded text.

- event list: notes the empty result is the literal `[]` and that
  structural empty-vs-non-empty against `[]` is the reliable presence
  predicate — not greping for in-payload strings.

No behavior change; --ai-help text only.

* cli: fix org quota help text + add dr enable/disable aliases (#298)

* cli: fix misleading org quota --quota help text

The --quota option claimed "0 to remove limit", implying 0 makes the
quota unlimited. The backend (doQuotaChange) sets the value as the org's
licensed sensor count (Stripe subscription quantity); 0 puts the org on
the free tier (no paid quota), the opposite of "remove limit". Updated
the option help and the AI explain text to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: add 'dr enable' and 'dr disable' aliases

D&R rules are hive-backed, but the dr command group lacked the
enable/disable convenience aliases that hive (and the per-hive
shortcuts) provide. Add them mirroring the hive pattern: read the
record metadata first (preserving tags/expiry/comment) and toggle only
usr_mtd.enabled, routing to the /mtd endpoint. Honors --namespace
(general/managed/service). Includes --ai-help explain text and unit
tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* CLI: discoverability & ergonomics improvements (projection flags, scaffolding, clearer verdicts) (#299)

* cli: discoverability & ergonomics improvements

Proactive developer-experience improvements found during routine review
of the CLI surface. Each change is small and self-contained.

- Global projection flags: add --fields, --sort-by, --reverse on the
  root group, mirroring the existing --filter module-level mechanism in
  output.py so they flow into every command's render path.
- api-key list --name: filter the key-hash-keyed result down to the
  single matching key while preserving the raw object shape.
- start-session: accept the hive://ai_agent/<name> URI form (the form
  the D&R 'start ai agent' action uses) in addition to a bare key.
- hive validate: emit an explicit positive verdict ("Record is valid."
  to stderr; {"valid": true} for json/yaml when the API is silent),
  keeping stdout machine-stable.
- hive set: add --tag-add/--tag-rm (additive), --comment, --expiry;
  metadata-only update when no data is supplied, overrides otherwise.
- secret set --value and a 'tag' subcommand (add/rm/set) on hive
  shortcut groups; --value documents the shell-history exposure.
- dr set --detect/--respond/--tag: assemble a rule from component files
  (mutually exclusive with --input-file).
- cloud-adapter/external-adapter list-types: derive supported adapter
  types from the cloud_sensor schema with a curated fallback; fix stale
  "...and others" prose that omitted threatlocker.
- hive schema: default to a flat field table (resolving $ref/$defs);
  raw JSON-Schema still available via --output json.
- event types: note that an empty result on a fresh org is expected
  (the schema is observed, not declared).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: generalize hive shortcut --value via per-hive value_key

The shared hive shortcut set command exposed --value wrapping the input as
{data: {secret: <value>}} for every hive, but only the secret hive uses a
single "secret" data field — the wrapper was meaningless (and wrong) for the
structured-data hives (lookup, fp, playbook, note, sop, adapters, ai-skill).

make_hive_group now takes an optional value_key naming the hive's single
scalar data field. --value is offered only when value_key is set (the secret
group declares value_key="secret") and wraps as {data: {<value_key>: <value>}}.
Structured-data hives no longer advertise --value at all.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: fix adapter list-types to enumerate real adapter types per hive

list-types derived its list from the bare JSON-Schema $defs keys, which are
helper structs (ClientOptions, AckBufferOptions, Dict, …), not adapter types —
so it printed garbage. The reflected schema is a root that $refs into the
record definition (CloudSensorRecord / ExternalAdapterConfig); the real type
names are that record's properties (s3, office365, threatlocker, …) minus the
sensor_type discriminator.

- Resolve the root $ref into the record and use its properties (fall back to
  inline root properties); never enumerate raw $defs keys.
- Parameterize by hive so cloud-adapter reads cloud_sensor and external-adapter
  reads external_adapter (their type sets genuinely differ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ai: resolve ai-sessions URL from per-org service URLs instead of hardcoding (#300)

The AI session CLI/SDK commands (start session, chat, auth claude, list/
get/terminate, attach, usage) hardcoded https://ai.limacharlie.io as the
ai-sessions host. The orgs/{oid}/url endpoint already returns a per-org
`ai` entry (lc.SiteURLs.Ai) whose value depends on the deployment -- e.g.
staging orgs return ai-staging.limacharlie.io -- so the hardcode pointed
staging orgs at the production service.

Resolve the host lazily from Organization.get_urls()["ai"] (cached, same
pattern as Search), prefixing https:// when needed and falling back to the
well-known production host only when the entry is absent. SessionAttachment
resolves the same way when no explicit base_url override is given.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: adapter schema --type / sensors --key + api-key create --store-secret (#301)

* cli: add adapter schema --type / sensors --key and api-key create --store-secret

Three FDE-acceleration additions found while profiling AI agent build runs:

- cloud-adapter/external-adapter `schema --type <t>`: resolve one adapter
  type's config sub-schema from the hive JSON-Schema $defs and render the flat
  field table (reusing hive's _flatten_schema). Shows where each field lives
  (e.g. hostname under client_options), preventing "unknown field" rejections
  from hand-built records. Unknown type errors with the valid type list.

- cloud-adapter/external-adapter `sensors --key <adapter>`: return the live
  sensor(s) an adapter produced by matching the record's installation_key (iid)
  against the sensor iid — the reliable way to get an adapter's SID without
  decoding the installation key. Falls back to hostname match; reports a clear
  "not registered yet" when the adapter hasn't delivered events.

- `api-key create --store-secret <name>` (+ --store-secret-tag): atomically mint
  the key and write its value into the secret hive, collapsing the
  mint -> capture -> store -> reference chain into one call (the value never
  has to transit an intermediate file).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: address review — quiet/json on adapter schema, server-side sensor selector, etag-safe store-secret

- adapter `schema --type` now respects --quiet (consistent with sensors/list-types)
  and its --output json carries the root $defs so nested $refs stay resolvable.
- adapter `sensors --key` filters server-side via a sensor selector
  (iid == "…" / hostname == "…") instead of paging and scanning every sensor.
- api-key create --store-secret reads any existing secret's etag and writes a
  conditional update (no silent lost-update; reports create vs overwrite), and on
  the no-value edge still surfaces the created key before failing.

Tests updated for the selector path and added: schema CLI table/unknown-type,
store-secret new-vs-existing(etag). 990 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: add org quota-usage command for getOrgQuotaUsage API (#302)

Adds support for the GET /quota_usage/{oid} endpoint (getOrgQuotaUsage),
exposing the enforced sensor quota usage the platform uses to gate sensors
coming online. Unlike the online sensor count, this weights EPP/response-mode
sensors, so it can read higher and is the correct value to size the sensor
quota against.

- SDK: Organization.get_quota_usage() returns {usage, quota, breakdown}
- CLI: `limacharlie org quota-usage` with --ai-help explain text
- Docs + unit tests (SDK, CLI, command-map lint, subcommand regression)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cli: add app hive shortcut command (#303)

Add an `app` hive-shortcut group mirroring the other hive shortcuts
(secret, lookup, playbook, note, sop, ...) for the new `app` hive added
in legion_config_hive. The app hive holds user-authored, AI-generated
mini web apps (a single self-contained HTML document rendered in a
sandboxed iframe by the web UI).

- New limacharlie/commands/app.py via make_hive_group("app", "app", "app")
  with app-specific --ai-help/explain text covering the record shape
  (display_name, html, required_permissions, allowed_origins,
  required_services, locations, expected_context).
- Register "app" in cli.py _COMMAND_MODULE_MAP.
- Update lazy-loading regression test (top-level set, module map,
  subcommands map, hive-shortcut load test).
- Document the shortcut in doc/cli/hive-data.md.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: remove stale cli-v2 from CI trigger branches

master is now the source of truth for the CLI v5 line; the cli-v2 branch
trigger is leftover and runs only redundant unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t7VKJidmVWW8d8EP3v6sD

---------

Co-authored-by: Chris Botelho <chris.botelho@limacharlie.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant